﻿using System;
using System.Collections.Generic;
using System.Linq;
using AcademicPlanner.Domain.Models;
using AcademicPlanner.Framework.Helpers;

namespace AcademicPlanner.Domain.Components
{
	/// <summary>
	/// MembershipMngr is the component of the AcademicPlanner application
	/// responsible for registration and membership.
	/// </summary>
	/// <version>1.0</version>
	/// <since>1.0</since>
    public class MembershipMngr : Component
    {
		private MembershipMngr() { }

		private class InstanceHolder 
		{
			internal static MembershipMngr INSTANCE = new MembershipMngr(); 
		}


		/// <summary>
		/// Gets the single MembershipMngr intance.
		/// </summary>
		/// <returns>The single MembershipMngr intance.</returns>
		/// <version>1.0</version>
		/// <since>1.0</since>
		public static MembershipMngr GetInstance()
		{ 
			return InstanceHolder.INSTANCE; 
		}


		/// <summary>
		/// Registers someone into the system. This method effectively
		/// creates a new <c>Person</c>, persist him to the data storage
		/// and sends out an email with a link to visit in order to finalize
		/// the registration.
		/// </summary>
		/// <param name="pEmail">The email address of the registeree.</param>
		/// <param name="pCompleRegistrationUrlFormat">
		/// A format of the link sent to the registeree in order to complete the registration;
		/// {0} is assumed for the registeree email address,
		/// {1} is assumed for the verification token,
		/// {2} is assumed for the new <c>Person</c>'s identifier.
		/// </param>
		/// <remarks>
		/// <c>NotCriticalDomainException</c>s may be thrown in case of biz logic violations
		/// such as a <c>Person</c> with the given email address already existing.
		/// <c>InvalidModelPropertiesException</c>s may be thrown in case of validation errors
		/// such as invalid email address given.
		/// <c>NotAuthorizedOperationException</c>s may be thrown in case of access control
		/// restrictions.
		/// </remarks>
		/// <version>1.0</version>
		/// <since>1.0</since>
        public void performRegisterTask(string pEmail, string pCompleRegistrationUrlFormat)
        {
			RegisterTask vTask = new RegisterTask(pEmail, pCompleRegistrationUrlFormat);
			vTask.Run();
        }

		/// <summary>
		/// Completes the registration of a <c>Person</c> by creating an <c>Account</c>
		/// for him with the supplied <c>User</c> info.
		/// </summary>
		/// <param name="pToken">The verification token (sent out in the registration email).</param>
		/// <param name="pPersonId">The <c>Person</c>'s identifier.</param>
		/// <param name="pEmail">The <c>Person</c>'s email address.</param>
		/// <param name="pPersonName">The <c>Person</c>'s full name.</param>
		/// <param name="pAccountUserName">The username for his <c>Account</c>'s <c>User</c>.</param>
		/// <param name="pAccountPassword">The password for his <c>Account</c>'s <c>User</c>.</param>
		/// <remarks>
		/// <c>NotCriticalDomainException</c>s may be thrown in case of biz logic violations
		/// such as a <c>Person</c> already having <c>Account</c>s.
		/// <c>InvalidModelPropertiesException</c>s may be thrown in case of validation errors
		/// such as invalid username or password given.
		/// <c>NotAuthorizedOperationException</c>s may be thrown in case of access control
		/// restrictions.
		/// </remarks>
		/// <version>1.0</version>
		/// <since>1.0</since>
		public void performSetupPersonTask(string pToken, int pPersonId, string pEmail, 
						string pPersonName, string pAccountUserName, string pAccountPassword)
		{
			var vPers = GetPersonToSetup(pToken, pPersonId, pEmail);
			if (vPers == null)
				throw new NotCriticalDomainException(DomainErrorCode.C_PERSON_TO_SETUP_NOT_FOUND);

			var vTask = new SetupPersonTask(vPers, pPersonName, pAccountUserName, pAccountPassword);
			vTask.Run();	
		}

		/// <summary>
		/// Gets a <c>Person</c> awaiting completion of his registration.
		/// </summary>
		/// <param name="pToken">The verification token (sent out in the registration email).</param>
		/// <param name="pPersonId">The <c>Person</c>'s identifier.</param>
		/// <param name="pEmail">The <c>Person</c>'s email address.</param>
		/// <returns>
		/// The <c>Person</c> awaiting completion of his registration of his registration or null
		/// if a matching <c>Person</c> was not found.
		/// </returns>
		/// <version>1.0</version>
		/// <since>1.0</since>
		public Person GetPersonToSetup(string pToken, int pPersonId, string pEmail)
		{
			Person rPers = null;

			if (DomainMaster.Auth.IsAuthenticationTokenValid(pToken, typeof(Person), pPersonId))
			{
				rPers = GetPerson(pPersonId);
				if (rPers != null && !rPers.Email.Equals(pEmail))
					rPers = null;
			}

			return rPers;
		}

		/// <summary>
		/// Gets the <c>Person</c> having the given identifier.
		/// </summary>
		/// <param name="pEntityId">The <c>Person</c>'s identifier.</param>
		/// <returns>The <c>Person</c> with the given identifier.</returns>
		/// <version>1.0</version>
		/// <since>1.0</since>
		public Person GetPerson(int pEntityId)
		{
			return DomainMaster.MainModel.AllPeople.WithId(pEntityId);
		}


		class SetupPersonTask : Task
		{
			Person person;
			string name;
			string accountUserName;
			string accountPassword;

			public SetupPersonTask(Person pPerson, string pName, string pAccountUserName, 
									string pAccountPassword)
			{
				person = pPerson;
				name = pName;
				accountUserName = pAccountUserName;
				accountPassword = pAccountPassword;
			}

			protected override void Perform()
			{
				// logic error checking
				if (person.Accounts.ToList().Count() > 0)
					throw new NotCriticalDomainException(DomainErrorCode.C_PERSON_ALREADY_SETUP);

				// update his data
				var vOp1 = new UpdatePersonOperation(person, name);
				vOp1.Launch();
				// create his initial account
				var vOp2 = new CreateAccountOperation(person, accountUserName, accountPassword);
				try
				{
					vOp2.Launch();
				}
				catch (InvalidModelPropertiesException vIx)
				{
					var vErrs = new InvalidModelPropertiesErrorCollection("Person");
					vErrs.AddInner("Account", vIx.Errors);
					throw new InvalidModelPropertiesException(DomainErrorCode.C_INVALID_PERSON_ACCOUNT_PROPERTIES,
																vErrs);
				}

				var vAcct = person.Accounts.ToList().First();
				// persit models
				vAcct.Persit();
				person.Persit();
				// setup the new person for Access Control
				DomainMaster.Auth.RegisterIntoACSystem(person, vAcct.User);
				// get rid of his AC authentication token
				DomainMaster.Auth.DiscardAuthenticationToken(person);
			}
		}

		class RegisterTask : Task
        {
			string email;
			string compleRegistrationUrlFormat;

			public RegisterTask(string pEmail, string pCompleRegistrationUrlFormat)
			{
				email = pEmail;
				compleRegistrationUrlFormat = pCompleRegistrationUrlFormat;
			}

			protected override void Perform()
			{
				var vOp = new CreatePersonOperation(email);

				// create person
				vOp.Launch();
				// persist
				vOp.Person.Persit();
				// send registration email
				if (!SendRegistrationEmail(vOp.Person))
					throw new NotCriticalDomainException(DomainErrorCode.C_REGISTRATION_EMAIL_FAILURE);
			}

			protected bool SendRegistrationEmail(Person pPerson)
			{
				var vRegistrationEmailFrom = (string)ConfigHelper.GetDomainSectionValue("emailSettings", 
													"registrationFromEmail");
				var vAppOwner = (string)ConfigHelper.GetDomainSectionValue("generalSettings",
													"appOwnerName");

				var vRegistrationEmailSubject = Resources.EmailTemplates.RegistrationEmailSubject;
				var vRegistrationEmailBodyTemplate = Resources.EmailTemplates.RegistrationEmailBody;

				var vToken = DomainMaster.Auth.GetAuthenticationToken(pPerson);
				var vRegistrationEmailLink = string.Format(compleRegistrationUrlFormat, pPerson.Email, 
															vToken, pPerson.EntityId);
				var vRegistrationEmailBody = string.Format(vRegistrationEmailBodyTemplate,
															vRegistrationEmailLink, vAppOwner);
				
				return EmailHelper.SendTextEmail(vRegistrationEmailFrom, pPerson.Email, 
													vRegistrationEmailSubject, vRegistrationEmailBody);
			}
        }

		/*class SaveAccountsOperation : Operation
		{
			protected IList<Account> accounts;
			protected IList<string> accountPasswords;
			protected IList<bool> activateds;

			public IList<Account> Accounts { get { return accounts; } }

			public SaveAccountsOperation()
			{

			}

			public SaveAccountsOperation(IList<Account> pAccts, IList<string> pAcctPswds)
			{
				accounts = pAccts;
				accountPasswords = pAcctPswds;
			}

			protected override void Execute()
			{
				for (int vI = 0; vI < accounts.Count; vI++)
				{
					accounts[vI].Set(activateds[vI], null, accountPasswords[vI]);
					accounts[vI].Persit();
				}
			}
		}*/

		class CreateAccountOperation : Operation
		{
			string accountUserName;
			string accountPassword;
			Person person;

			public CreateAccountOperation(Person pPerson, string pAccountUserName, string pAccountPassword)
			{
				person = pPerson;
				accountUserName = pAccountUserName;
				accountPassword = pAccountPassword;
			}

			protected override void Init()
			{
				// check authorization
				if (!DomainMaster.Auth.IsUserAuthorized(ACOperationName.CREATE_ACCOUNT))
					throw new NotAuthorizedOperationException(DomainErrorCode.C_NOT_AUTHORIZED_CREATE_ACCOUNT);

				base.Init();
			}

			protected override void Execute()
			{
				var vAct = DomainMaster.MainModel.AllAccounts.WithUserName(accountUserName);
				if (vAct != null)
					throw new InvalidModelPropertiesException("User", "UserName",
							InvalidModelPropertyErrorCode.NOT_UNIQUE,
							DomainErrorCode.C_NOT_UNIQUE_ACCOUNT_USERNAME);

				vAct = DomainMaster.ModelsFountain.GetNewAccount();
				vAct.Set(true, accountUserName, accountPassword);
				person.AddAccount(vAct);
			}
		}

        class UpdatePersonOperation : Operation
        {
			protected Person person;
			protected string name;
			protected string email;

			public Person Person { get { return person; } }

			public UpdatePersonOperation()
			{
				
			}

			public UpdatePersonOperation(Person pPers, string pName)
			{
				person = pPers;
				name = pName;
			}

			protected override void Execute()
			{
				// set properties
				person.Set(email, name);
			}
        }

        class CreatePersonOperation : UpdatePersonOperation
        {
			public CreatePersonOperation(string pEmail)
			{
				email = pEmail;
			}

			protected override void Init()
			{
				// check authorization
				if (!DomainMaster.Auth.IsUserAuthorized(ACOperationName.CREATE_PERSON))
					throw new NotAuthorizedOperationException(DomainErrorCode.C_NOT_AUTHORIZED_CREATE_PERSON);

				base.Init();
			}

			protected override void Execute()
			{
				person = DomainMaster.MainModel.AllPeople.WithEmail(email);
				// make sure email is unique among completely registered people
				if (person != null && !person.Name.Equals(string.Empty))
					throw new InvalidModelPropertiesException("Person", "Email", 
							InvalidModelPropertyErrorCode.NOT_UNIQUE, 
							DomainErrorCode.C_NOT_UNIQUE_PERSON_EMAIL);
				else if (person == null)
				{
					person = DomainMaster.ModelsFountain.GetNewPerson();
					base.Execute();
				}
			}
        }

    }
}
